package com.hengyu.oauth2.core;

import com.hengyu.oauth2.entity.HengYuOAuth2AccessToken;
import com.hengyu.oauth2.entity.HengYuOAuth2RefreshToken;
import com.hengyu.oauth2.jpa.HengYuAccessTokenJPA;
import com.hengyu.oauth2.jpa.HengYuRefreshTokenJPA;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.security.oauth2.common.ExpiringOAuth2RefreshToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2RefreshToken;
import org.springframework.security.oauth2.common.util.SerializationUtils;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.AuthenticationKeyGenerator;
import org.springframework.security.oauth2.provider.token.DefaultAuthenticationKeyGenerator;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

/**
 * 自定义oauth2验证机制实现了TokenStore接口
 * 项目名称：OAuth2Example
 * 项目版本：V1.0
 * 包名称：com.example.oauth
 * 创建人：yuqy
 * 创建时间：2017/3/16 18:33
 * 修改人：yuqy
 * 修改时间：2017/3/16 18:33
 * 修改备注：
 */
@Repository
@Transactional
public class HengYuOAuth2TokenStore implements TokenStore
{
    //日志对象
    private Log LOG = LogFactory.getLog(HengYuOAuth2TokenStore.class);

    //注入accessToken数据接口
    @Autowired
    private HengYuAccessTokenJPA accessTokenDAO;
    //注入refreshToken数据接口
    @Autowired
    private HengYuRefreshTokenJPA refreshTokenDAO;
    //数据库中字段Authentication_id生成器
    private AuthenticationKeyGenerator authenticationKeyGenerator = new DefaultAuthenticationKeyGenerator();

    @Override
    public OAuth2Authentication readAuthentication(OAuth2AccessToken token) {
        OAuth2Authentication authentication = null;
        try {
            List<HengYuOAuth2AccessToken> tokens = accessTokenDAO.findAll(new Specification<HengYuOAuth2AccessToken>() {
                @Override
                public Predicate toPredicate(Root<HengYuOAuth2AccessToken> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                    criteriaQuery.where(criteriaBuilder.equal(root.get("tokenId"),token.getValue()));
                    return null;
                }
            });
            if(tokens != null && tokens.size() > 0) {
                authentication = SerializationUtils.deserialize(tokens.get(0).getAuthentication());
            }

        }
        catch (EmptyResultDataAccessException e) {
            if (LOG.isInfoEnabled()) {
                LOG.info("Failed to find access token for token " + token);
            }
        }

        return authentication;
    }

    @Override
    public OAuth2Authentication readAuthentication(String s) {
        return null;
    }

    @Override
    public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {

        //删除之前存在的access_token
        //查询是否存在该authentication_id的access_token
        List<HengYuOAuth2AccessToken> tokens = accessTokenDAO.findAll(new Specification<HengYuOAuth2AccessToken>() {
            @Override
            public Predicate toPredicate(Root<HengYuOAuth2AccessToken> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                criteriaQuery.where(criteriaBuilder.equal(root.get("authenticationId"),authenticationKeyGenerator.extractKey(authentication)));
                return null;
            }
        });
        //临时变量
        HengYuOAuth2AccessToken deleteToken = null;
        //判断是否查询出access_token
        if(tokens != null && tokens.size() > 0)
        {
            deleteToken = tokens.get(0);
        }
        //如果存在查询结果，删除该查询token
        if(deleteToken != null)
        {
            accessTokenDAO.delete(deleteToken);
        }

        String refreshToken = null;
        if (token.getRefreshToken() != null) {
            refreshToken = token.getRefreshToken().getValue();
        }
        HengYuOAuth2AccessToken at=new HengYuOAuth2AccessToken();
        at.setTokenId(token.getValue());
        at.setToken(SerializationUtils.serialize(token));
        at.setAuthenticationId(authenticationKeyGenerator.extractKey(authentication));
        at.setAuthentication(SerializationUtils.serialize(authentication));
        at.setRefreshToken(refreshToken);
        at.setClientId(authentication.getOAuth2Request().getClientId());
        at.setUserName(authentication.isClientOnly()?null:authentication.getName());
        accessTokenDAO.save(at);
    }

    @Override
    public OAuth2AccessToken readAccessToken(String tokenValue) {
        OAuth2AccessToken accessToken = null;

        try {
            List<HengYuOAuth2AccessToken> tokens = accessTokenDAO.findAll(new Specification<HengYuOAuth2AccessToken>() {
                @Override
                public Predicate toPredicate(Root<HengYuOAuth2AccessToken> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                    criteriaQuery.where(criteriaBuilder.equal(root.get("tokenId"),tokenValue));
                    return null;
                }
            });
            if(tokens!=null && tokens.size()>0) {
                accessToken = SerializationUtils.deserialize(tokens.get(0).getToken());
            }
        }
        catch (EmptyResultDataAccessException e) {
            if (LOG.isInfoEnabled()) {
                LOG.info("Failed to find access token for token " + tokenValue);
            }
        }

        return accessToken;
    }

    @Override
    public void removeAccessToken(OAuth2AccessToken oAuth2AccessToken) {
        List<HengYuOAuth2AccessToken> tokens = accessTokenDAO.findAll(new Specification<HengYuOAuth2AccessToken>() {
            @Override
            public Predicate toPredicate(Root<HengYuOAuth2AccessToken> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                criteriaQuery.where(criteriaBuilder.equal(root.get("tokenId"),oAuth2AccessToken.getValue()));
                return null;
            }
        });
        //临时变量
        HengYuOAuth2AccessToken deleteToken = null;
        //判断是否查询出access_token
        if(tokens != null && tokens.size() > 0)
        {
            deleteToken = tokens.get(0);
        }
        //如果存在查询结果，删除该查询token
        if(deleteToken != null)
        {
            accessTokenDAO.delete(deleteToken);
        }
    }

    @Override
    public void storeRefreshToken(OAuth2RefreshToken refreshToken, OAuth2Authentication authentication) {
        HengYuOAuth2RefreshToken rt = new HengYuOAuth2RefreshToken();
        rt.setTokenId(refreshToken.getValue());
        rt.setToken(SerializationUtils.serialize(refreshToken));
        rt.setAuthentication(SerializationUtils.serialize(authentication));
        refreshTokenDAO.save(rt);
    }

    @Override
    public OAuth2RefreshToken readRefreshToken(String tokenValue) {
        ExpiringOAuth2RefreshToken refreshToken = null;
        try {
            refreshToken = SerializationUtils.deserialize(refreshTokenDAO.findOne(tokenValue).getToken());
        }
        catch (EmptyResultDataAccessException e) {
            if (LOG.isInfoEnabled()) {
                LOG.info("Failed to find refresh token for token " + tokenValue);
            }
        }

        return refreshToken;
    }

    @Override
    public OAuth2Authentication readAuthenticationForRefreshToken(OAuth2RefreshToken token) {
        OAuth2Authentication authentication = null;

        try {
            authentication = SerializationUtils.deserialize(refreshTokenDAO.findOne(token.getValue()).getAuthentication());
        }
        catch (EmptyResultDataAccessException e) {
            if (LOG.isInfoEnabled()) {
                LOG.info("Failed to find access token for token " + token);
            }
        }

        return authentication;
    }

    @Override
    public void removeRefreshToken(OAuth2RefreshToken oAuth2RefreshToken) {
        refreshTokenDAO.delete(oAuth2RefreshToken.getValue());
    }

    @Override
    public void removeAccessTokenUsingRefreshToken(OAuth2RefreshToken oAuth2RefreshToken) {
        refreshTokenDAO.delete(oAuth2RefreshToken.getValue());
    }

    @Override
    public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) {
        OAuth2AccessToken accessToken = null;

        try {
            String auth = authenticationKeyGenerator.extractKey(authentication);
            HengYuOAuth2AccessToken at=accessTokenDAO.findOne(auth);
            if(null==at){
                return null;
            }else{
                accessToken = SerializationUtils.deserialize(at.getToken());
            }

        }
        catch (EmptyResultDataAccessException e) {
            if (LOG.isInfoEnabled()) {
                LOG.debug("Failed to find access token for authentication " + authentication);
            }
        }

        return accessToken;
    }

    @Override
    public Collection<OAuth2AccessToken> findTokensByClientIdAndUserName(String clientId, String userName) {
        Object accessToken = new ArrayList();

        try {

            accessToken = accessTokenDAO.findAll(new Specification<HengYuOAuth2AccessToken>() {
                @Override
                public Predicate toPredicate(Root<HengYuOAuth2AccessToken> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                    criteriaQuery.where(criteriaBuilder.equal(root.get("clientId"),clientId),criteriaBuilder.equal(root.get("userName"),userName));
                    return null;
                }
            });
        } catch (EmptyResultDataAccessException var5) {
            if(LOG.isInfoEnabled()) {
                LOG.info("Failed to find access token for clientId " + clientId + " and userName " + userName);
            }
        }

        List<OAuth2AccessToken> accessTokens = this.removeNulls((List)accessToken);
        return accessTokens;
    }

    @Override
    public Collection<OAuth2AccessToken> findTokensByClientId(String clientId) {
        Object accessToken = new ArrayList();

        try {
            accessToken = accessTokenDAO.findAll(new Specification<HengYuOAuth2AccessToken>() {
                @Override
                public Predicate toPredicate(Root<HengYuOAuth2AccessToken> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                    criteriaQuery.where(criteriaBuilder.equal(root.get("clientId"),clientId));
                    return null;
                }
            });
        } catch (EmptyResultDataAccessException var4) {
            if(LOG.isInfoEnabled()) {
                LOG.info("Failed to find access token for clientId " + clientId);
            }
        }

        List<OAuth2AccessToken> accessTokens = this.removeNulls((List)accessToken);
        return accessTokens;
    }

    private List<OAuth2AccessToken> removeNulls(List<OAuth2AccessToken> accessTokens) {
        List<OAuth2AccessToken> tokens = new ArrayList();
        Iterator var3 = accessTokens.iterator();

        while(var3.hasNext()) {
            OAuth2AccessToken token = (OAuth2AccessToken)var3.next();
            if(token != null) {
                tokens.add(token);
            }
        }

        return tokens;
    }
}
